import * as fs from "fs"
import { promisify } from "util"
import { addRoute, MemoryTree, RouteItem, template } from "f2e-server3";
import { NativeResponse } from "f2e-server3/lib/server-engine/native/response"
import CleanCSS from "clean-css";
import { UserApp, UserAppDetail } from "../interface"
import html_template from "./template";
import { getContentPath } from "../actions/tree"
import { getAppDetail, hasAuthority } from "../actions/app";
import { getDocDir, getLoginUser } from "../config";
import archiver from 'archiver'
import unzipper from 'unzipper'
import path from "path";
import { API, DIR_TYPE, stringify } from "../common";
import * as marked from "marked"
import { getProfile } from "../actions/profile";
import { IncomingMessage } from "http";

const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
const mkdir = promisify(fs.mkdir)
const rm = promisify(fs.rm)

const META_PREFIX = "__META__="

const getPreviewMeta = async (app: UserApp, detail: UserAppDetail) => {
    const docs: Record<number, string> = {}
    const used_files = new Set<string>()
    for (let i = 0; i < detail.tree.length; i++) {
        const { id } = detail.tree[i];
        const contentPath = getContentPath(app.id, id)
        const content = await readFile(contentPath, 'utf-8')
        docs[id] = content
        marked.use({
            renderer: {
                link: function ({ href }) {
                    if (/^files\//.test(href)) {
                        used_files.add(href.replace(/^files\//, ''))
                    }
                    return false
                },
                image: function ({ href }) {
                    if (/^files\//.test(href)) {
                        used_files.add(href.replace(/^files\//, ''))
                    }
                    return false
                }
            }
        })
        marked.marked(content)
    }

    // clear files
    fs.readdir(path.join(getDocDir(), app.id + "", DIR_TYPE.FILES), (err, files) => {
        if (err) {
            return
        }
        for (let i = 0; i < files.length; i++) {
            const file = files[i];
            if (!used_files.has(file)) {
                rm(path.join(getDocDir(), app.id + "", DIR_TYPE.FILES, file))
            }
        }
    })

    const meta: PreviewMeta = {
        detail, docs, app,
    }
    return meta
}

type PreviewMeta = {
    detail: UserAppDetail,
    docs: Record<number, string>,
    app: UserApp,
}

const getPreviewHtml = async (app: UserApp, store: MemoryTree.Store) => {
    const origin_map = store.origin_map
    const style_index = origin_map.get('src/index.css')?.data
    const script_preview = origin_map.get('src/preview.tsx')?.data

    return template(html_template, {
        title: app.title,
        description: app.description?.replace(/\{\{|\}\}/g, ""),
        author: app.editors?.join(","),
        createdAt: app.createdAt,
        updatedAt: app.updatedAt,
        style: new CleanCSS({}).minify(style_index + "").styles,
        script: script_preview,
    })
}

addRoute("preview/:app_id/meta.js", async (_, ctx) => {
    const { app_id } = ctx.params
    if (!/^[0-9]+$/.test(app_id)) {
        return "站点不存在"
    }
    if (!ctx.store) {
        return "启动中，请稍后再试"
    }
    const loginUser = getLoginUser(ctx)
    if (!loginUser) {
        return "请先登录"
    }
    const res = await getAppDetail(loginUser, parseInt(app_id))
    if (res.success) {
        const { app, detail } = res.data
        const meta = await getPreviewMeta(app, detail)
        return `__META__=${JSON.stringify(meta)}`
    } else {
        return `__META__={}`
    }
}, { type: "raw", sourceType: "js" })

addRoute("preview/:app_id/files/:file_name", async (_, ctx) => {
    const { app_id, file_name } = ctx.params
    if (!/^[0-9]+$/.test(app_id)) {
        return "站点不存在"
    }
    if (!ctx.store) {
        return "启动中，请稍后再试"
    }
    const loginUser = getLoginUser(ctx)
    if (!loginUser) {
        return "请先登录"
    }
    return fs.readFileSync(path.join(getDocDir(), app_id, DIR_TYPE.FILES, file_name))
}, { type: "raw", sourceType: "binary" })

addRoute("preview/:app_id", async (_, ctx) => {
    const { app_id } = ctx.params
    if (!/^[0-9]+$/.test(app_id)) {
        return "站点不存在"
    }
    if (!ctx.store) {
        return "启动中，请稍后再试"
    }
    const loginUser = getLoginUser(ctx)
    if (!loginUser) {
        return "请先登录"
    }
    const profile = await getProfile()
    const app = profile.apps.find(t => t.id === Number(app_id) && (
        t.createdBy === loginUser.username
        || t.editors.includes(loginUser.username)
    ))
    if (!app) {
        return "站点不存在"
    }
    return getPreviewHtml(app, ctx.store)
}, { type: "raw", sourceType: "html" })

addRoute("export/:app_id", async (_, ctx) => {
    const resp = ctx.resp as NativeResponse
    const handleError = (message: string) => {
        resp.cork(function () {
            resp.writeStatus("500 Internal Server Error")
            resp.end(message)
        })
    }
    const { app_id } = ctx.params

    if (!/^[0-9]+$/.test(app_id)) {
        return handleError("站点不存在")
    }
    if (!ctx.store) {
        return handleError("启动中，请稍后再试")
    }
    const res = await getAppDetail(false, parseInt(app_id))
    if (res.success) {
        const { app, detail, path_detail } = res.data
        const page_index = await getPreviewHtml(app, ctx.store)
        const meta = await getPreviewMeta(app, detail)
        const archive = archiver('zip');
        resp.writeHeader('Content-Type', 'application/zip');
        resp.writeHeader('Content-Disposition', `attachment; filename=${encodeURIComponent(app.title)}.zip`);
        archive.pipe(resp.response)
        archive.append(page_index, { name: "index.html", prefix: DIR_TYPE.DOCS })
        archive.append(`${META_PREFIX}${JSON.stringify(meta)}`, { name: "meta.js", prefix: DIR_TYPE.DOCS })
        archive.directory(path.join(path.dirname(path_detail), DIR_TYPE.FILES), DIR_TYPE.DOCS + "/" + DIR_TYPE.FILES);
        archive.finalize();
    } else {
        return handleError(res.message + "")
    }

}, { type: "none", sourceType: "zip" })

export const onImport: RouteItem["handler"] = async function (_, ctx) {
    const loginUser = getLoginUser(ctx)
    if (!loginUser) {
        return API.error("请先登录")
    }
    const params = new URLSearchParams(ctx.req.getQuery())
    const app_id = Number(params.get("app_id") || "")
    if (!app_id) {
        return API.error("缺少站点编号")
    }
    const profile = await getProfile()
    const app = profile.apps.find(item => item.id === app_id && hasAuthority(loginUser, item))
    if (!app) {
        return API.error("站点不存在")
    }
    const path_base = path.join(getDocDir(), app_id + "")
    const temp_dir = path.join(path_base, DIR_TYPE.TEMP)
    const temp_docs = path.join(temp_dir, DIR_TYPE.DOCS)
    if (!fs.existsSync(temp_dir)) {
        fs.mkdirSync(temp_dir, { recursive: true })
    }
    const req = (ctx.req as any).request as IncomingMessage
    return new Promise((resolve) => {
        req.pipe(
            unzipper.Extract({
                path: temp_dir,
            })
        )
            .on('finish', async () => {
                const temp_meta = path.join(temp_docs, "meta.js")
                if (!fs.existsSync(temp_meta)) {
                    resolve(API.error("格式不正确，请使用站点导出功能导出的文件！"))
                    return
                }
                const meta_str = await readFile(temp_meta, 'utf-8')
                const meta_json = meta_str.toString().slice(META_PREFIX.length)
                let meta: PreviewMeta | undefined = undefined
                try {
                    meta = JSON.parse(meta_json)
                } catch (e) { }
                if (!meta || !meta.detail || !meta.docs) {
                    resolve(API.error("格式不正确，请使用站点导出功能导出的文件！"))
                    return
                }
                const temp_files = path.join(temp_docs, DIR_TYPE.FILES)
                const path_detail = path.join(path_base, DIR_TYPE.PROFILE)
                const path_files = path.join(path_base, DIR_TYPE.FILES)
                const path_docs = path.join(path_base, DIR_TYPE.DOCS)

                await writeFile(path_detail, stringify(meta.detail))
                if (fs.existsSync(temp_files)) {
                    await rm(path_files, { recursive: true })
                    fs.renameSync(temp_files, path_files)
                }
                if (!fs.existsSync(path_docs)) {
                    await mkdir(path_docs, { recursive: true })
                }
                await Promise.all(
                    Object.entries(meta.docs || {})
                        .map(([i, content]) => writeFile(path.join(path_docs, i + '.md'), content))
                )
                await rm(temp_docs, { recursive: true })
                resolve(API.success(meta.detail))
            })
            .on('error', err => {
                resolve(API.error(err.message))
            })
    })
}